//==============================================================================
//
//      DConfig - the config parser, writer and storage in the ofc-library
//
//               Copyright (C) 2004  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2008-08-24 05:37:06 $ $Revision: 1.10 $
//
//==============================================================================

#include "ofc/DConfig.h"

#include <string.h>
#include <stdio.h>

#if _INTERFACE_

#include "ofc/config.h"
#include "ofc/DLexer.h"

#ifdef HAVE_DLEXER
# define HAVE_DCONFIG (1)
#endif


#include "ofc/DTree.h"

#ifdef HAVE_DCONFIG

#define DCH_CLOSE_EXPECTED   (1) // end of section ']' expected
#define DCH_SECTION_EXPECTED (2) // section name expected
#define DCH_ASSIGN_EXPECTED  (3) // : or = expected after option name
#define DCH_CHAR_UNEXPECTED  (4) // unexpected character


//
// The DConfigHandler protocol specifies the methods a config handler
// must implement for processing the contents of a config file.
//

@protocol DConfigHandler

//
// Process the start of the config file
// 
// @return success
// 
- (BOOL) startConfig;

//
// Process the end of the config file
// 
// @return success
//
- (BOOL) endConfig;

//
// Process a (start of a) section
// 
// @param name   the name of the section
// 
// @return success
// 
- (BOOL) section :(const char *) name;

//
// Process an option
// 
// @param section  the name of the section
// @param name     the name of the option
// @param value    the value of the option
// 
// @return success
// 
- (BOOL) option :(const char *) section :(const char *) name :(const char *) value;

//
// Process a comment in the config file
// 
// @param comment  the comment text
// 
// @return success
// 
- (BOOL) comment :(const char *) comment;

//
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 
- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;

@end

//
// The DConfigReader class implements methods for parsing a config file.
//
// @example
// #include <stdio.h>
// #include "ofc/DConfig.h"
// #include "ofc/DFile.h"
// 
// @interface MyHandler : DObject <DConfigHandler>  // MyHander implements the DConfigHander interface
// {
// }
// - (BOOL) startConfig;
// - (BOOL) endConfig;
// - (BOOL) section :(const char *) name;
// - (BOOL) option :(const char *) section :(const char *) name :(const char *) value;
// - (BOOL) comment :(const char *) comment;
// - (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;
// @end
// 
// @implementation MyHandler
// 
// - (BOOL) startConfig
// {
//   return YES;                          // No interrest in startConfig..
// }
// 
// - (BOOL) endConfig
// {
//   return YES;                          // No interest in endConfig..
// }
// 
// - (BOOL) section :(const char *) name
// {
//   return YES;                          // No interest in (start of) section
// }
// 
// - (BOOL) option :(const char *) section :(const char *) name :(const char *) value
// {
//   printf("Section:%s Name:%s Value:%s\n", section, name, value);
//   return YES;
// }
// 
// - (BOOL) comment :(const char *) comment
// {
//   return YES;                          // No interest in comment
// }
// 
// - (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber
// {
//   printf("Error:%d in %s on line %d and column %d.\n", number, name, lineNumber, columnNumber);
// }
// 
// @end
// 
// 
// int main(int argc, char *argv[])
// {
//   MyHandler     *hdlr = [MyHandler     new];  // Config handler
//   DConfigReader *rdr  = [DConfigReader new];  // Config reader
//   DFile         *file = [DFile         new];
// 
//   if ([file open :"example.ini" :"r"]) // Open the config file
//   {
//     if (![rdr parse :file :"example.ini" :hdlr]) // Parse the config file, calling the config handler methods
//     {
//       printf("File \"example.ini\" could not be parsed.\n");
//     }
// 
//     [file close];
//   }
// 
//   [hdlr free];                         // Cleanup
//   [file free];
//   [rdr  free];
// 
//   return 0;
// }
// 

@interface DConfigReader : DObject
{
@private
  DLexer              *_lexer;   // the lexer used to parse the file
}
#endif

#endif


#ifdef HAVE_DCONFIG



@implementation DConfigReader

//// Constructors

//
// Initialise a config reader
//
// @return the object
//

- (DConfigReader *) init
{
  [super init];

  _lexer   = [DLexer new];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//

- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
    
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  [_lexer free];
  
  return [super free];
}


//// Parser methods


#if _PRIVATE_

//
// Report an error 
// 
// @param number    the error number
// @param lexer     the lexer with the source
// @param handler   the config file handler
// 
// @return none
// 

static void error(int number, DLexer *lexer, id <DConfigHandler> handler)
{
  [handler error :number :[lexer name] :[lexer lineNumber] :[lexer columnNumber]];
}
  
//
// Parse the config file
// 
// @param lexer     the lexer with the source
// @param handler   the config file handler
// 
// @return success
// 

static void parseFile(DLexer *lexer, id <DConfigHandler> handler)
{
  DText *section = [DText new];
  DText *option  = [DText new];
  DText *value   = [DText new];
  
  [section set :"EMPTY"];

  [handler startConfig];
  
  while (![lexer isEof])
  {
    [lexer nextWhiteSpace];
    
    if ([lexer nextString :";"] || [lexer nextString :"#"])
    {
      [lexer nextExpression :"[[:space:]]?"];
      
      [lexer nextExpression :".*"];
      
      [handler comment :[lexer text]];
    }
    else if ([lexer nextString :"["])
    {
      [lexer nextWhiteSpace];
      
      if ([lexer nextExpression :"[a-zA-Z][a-zA-Z0-9_]*"])
      {
        [section set :[lexer text]];
      
        [lexer nextWhiteSpace];
        
        if ([lexer nextString :"]"])
        {
          [handler section :[section cstring]];
        }
        else
        {
          error(DCH_CLOSE_EXPECTED, lexer, handler);
        }
      }
      else
      {
        error(DCH_SECTION_EXPECTED, lexer, handler);
      }
        
    }
    else if ([lexer nextExpression :"[a-zA-Z][a-zA-Z0-9_]*"])
    {
      
      [option set :[lexer text]];
      
      [lexer nextWhiteSpace];
      
      if ([lexer nextString :"="] || [lexer nextString :"="])
      {
        [lexer nextWhiteSpace];
        
        [lexer nextExpression :".*"];
        
        [value set :[lexer text]];
        
        [handler option :[section cstring] :[option cstring] :[value cstring]];
      }
      else
      {
        error(DCH_ASSIGN_EXPECTED, lexer, handler);
      }
    }
    else
    {
      error(DCH_CHAR_UNEXPECTED, lexer, handler);
    }
      
    [lexer nextLine];
  }

  [handler endConfig];

  [section free];
  [option  free];
  [value   free];
}

#endif

//
// Parse a config file
// 
// @param source   the source of the config file
// @param name     the name of the config file
// @param handler  the config handler
// 
// @return success
// 

- (BOOL) parse :(id <DTextReadable>) source :(const char *) name :(id <DConfigHandler>) handler
{
  BOOL ok = NO;
  
  if (handler == nil)
  {
    WARNING(DW_INVALID_ARG, "handler");
  }
  else if (source == nil)
  {
    WARNING(DW_INVALID_ARG, "source");
  }
  else
  {
    [_lexer source :source :name];
  
    parseFile(_lexer, handler);
    
    ok = YES;
  }
  
  return ok;
}


@end
#endif


  
#if _INTERFACE_

#ifdef HAVE_DCONFIG
//
// The config writer class implements methods for writing a config file.
//
// @example
// #include <stdio.h>
// #include "ofc/DConfig.h"
// 
// int main(int argc, char *argv[])
// {
//   DConfigWriter *wrtr = [DConfigWriter new];
//   DText         *dest = [DText         new];
// 
//   if (![wrtr startConfig :dest])       // Use text string as destination
//     printf("Error in startConfig.\n");
// 
//   if (![wrtr comment :"Example config file"]) // Write a comment
//     printf("Error in comment.\n");
// 
//   if (![wrtr section :"Section1"])      // Write a section in the 'config file'
//     printf("Error in section.\n");
//   
//   if (![wrtr option :"Section1" :"Option1" :"Value1"]) // Write an option in the 'file'
//     printf("Error in option.\n");
// 
//   if (![wrtr option :"Section1" :"Option2" :"Value2"])
//     printf("Error in option.\n");
// 
//   if (![wrtr option :"Section2" :"Option1" :"Value1"]) // Write an option in the next section
//     printf("Error in option.\n");
// 
//   [wrtr endConfig];
// 
//   printf("Config file:\n%s", [dest cstring]);
// 
//   [dest free];                         // Cleanup
//   [wrtr free];
// 
//   return 0;
// }
// 

@interface DConfigWriter : DObject <DConfigHandler>
{
@private
  id <DTextWritable>  _dest;     // the config file
  DText              *_section;  // the current section
}
#endif
#endif



#ifdef HAVE_DCONFIG

@implementation DConfigWriter


//// Constructors

//
// Initialise a config writer
//
// @return the object
//

- (DConfigWriter *) init
{
  [super init];

  _dest    = nil;
  _section = [DText new];
  
  return self;
}

//
// Initialise a config writer with a config file
// 
// @param destination  the config file
//
// @return the object
//

- (DConfigWriter *) init :(id <DTextWritable>) destination
{
  [self init];

  [self startConfig :destination];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
    
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  [_section free];
  
  return [super free];
}


//// Writer methods

//
// Start the writing of a config file
// 
// @param destination  the config file
// 
// @return the object
// 

- (BOOL) startConfig :(id <DTextWritable>) destination
{
  if (destination != nil)
  {
    _dest = destination;
  }
  else
    WARNING(DW_INVALID_ARG, "destination");
  
  return (destination != nil);
}

//
// Process the start of the config file
// 
// @return success
// 

- (BOOL) startConfig
{
  // not much to do
 
  return YES;
}

//
// Process the end of the config file
// 
// @return success
//

- (BOOL) endConfig
{
  // not much to do
  
  return YES;
}

//
// Write the (start of a) section
// 
// @param name   the name of the section
// 
// @return success
// 

- (BOOL) section :(const char *) name
{
  BOOL ok = NO;
  
  if ((_dest != nil) && (name != NULL))
  {
    [_section set :name];
    
    ok  = [_dest writeChar :'[' ];
    ok &= [_dest writeText :name];
    ok &= [_dest writeChar :']' ];
    ok &= [_dest writeLine :""  ];
  }
  
  return ok;
}

//
// Write an option
// 
// @param section  the name of the section
// @param name     the name of the option
// @param value    the value of the option
// 
// @return success
// 

- (BOOL) option :(const char *) section :(const char *) name :(const char *) value
{
  BOOL ok = NO;
  
  if ((_dest != nil) && (name != NULL) && (value != NULL))
  {
    if (section != NULL)
    {
      if ([_section icompare :section] != 0)
      {
        [self section :section];
      }
    }
    
    ok  = [_dest writeText :name ];
    ok &= [_dest writeChar :'='  ];
    ok &= [_dest writeLine :value];
  }

  return ok;
}
    
//
// Write a comment in the config file
// 
// @param comment  the comment text
// 
// @return success
// 

- (BOOL) comment :(const char *) comment
{
  BOOL ok = NO;
  
  if (_dest != nil)
  {
#ifdef WIN32
    ok  = [_dest writeText :"; "];
#else
    ok  = [_dest writeText :"# "];
#endif
    if (comment != NULL)
    {
      ok &= [_dest writeLine :comment];
    }
    else
      ok &= [_dest writeLine :""];
  }
  
  return ok;
}

//
// Report an error during the writing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 

- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;
{
  fprintf(stderr, "%s, line %d.%d: code:%d\n", name, lineNumber, columnNumber, number);
}
        
@end
#endif

#if _INTERFACE_

#ifdef HAVE_DCONFIG
//
// The ConfigTree class implements methods for storing config options,
// including reading and writing to a config file, and changing and
// removing options. Config options are structured in sections with 
// multiple options with values.
//
// @example
// #include <stdio.h>
// #include "ofc/DConfig.h"
// #include "ofc/DFile.h"
// 
// int main(int argc, char *argv[])
// {
//   DConfigTree *tree = [DConfigTree new];
//   DFile       *file = [DFile       new];
//   
//   char         name1[] = "example.ini";
//   char         name2[] = "example2.ini";
// 
//   if ([file open :name1 :"r"])         // Open the config file
//   {
//     if ([tree read :file :name1])      // Read the config file
//     {
//       [file close];
// 
//       printf("Config %s the option Option1 in SectionA.\n",
//           ([tree has :"SectionA" :"Option1"] ? "has" : "has not")); // Check for an option
// 
//       if ([tree set :"SectionB" :"Option2" :"Value2"])  // Insert an option
//         printf("SectionB - Option2 succesfully inserted.\n");
//       else
//         printf("Could not insert an option for sectionB.\n");
// 
//       if ([tree remove :"SectionA" :"Option2"])  // Remove an option
//         printf("SectionA - Option2 succesfully removed.\n");
//       else
//         printf("Could not remove option2 for sectionA.\n");
// 
//       if ([file open :name2 :"w"])     // Open the config file for writing
//       {
//         if ([tree write :file :name2]) // Write the modified config file
//           printf("Config tree succesfully written in \"%s\".\n", name2);
//         else
//           printf("Could not write \"%s\".\n", name2);
// 
//         [file close];
//       }
//       else
//         printf("Could not write \"%s\".\n", name2);
//     }
//     else
//     {
//       [file close];
//       printf("Could not read \"%s\".\n", name1);
//     }
//   }
//   else
//     printf("Could not open \"%s\":%d.\n", name1, [file error]);
// 
//   [file free];                         // Cleanup
//   [tree free];
// 
//   return 0;
// }
// 

@interface DConfigTree : DObject <DConfigHandler>
{
@private
  DTree         *_config; // the options in a tree of sections
  DTreeIterator *_iter;   // the iterator in the config tree
}
#endif
#endif


#ifdef HAVE_DCONFIG

@implementation DConfigTree


//// Constructors

//
// Initialise a config tree
//
// @return the object
//

- (DConfigTree *) init
{
  [super init];

  _config   = [DTree new];
  
  _iter     = [DTreeIterator alloc];
  
  [_iter init :_config];
  
  return self;
}

//
// Initialise a config tree with a config source
// 
// @param source     the config source
// @param filename   the name of the config source
//
// @return the object
//

- (DConfigTree *) init :(id <DTextReadable>) source :(const char *) name
{
  [self init];

  [self read :source :name];
  
  return self;
}


//// Copy constructor

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");
    
  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the object
//

- free
{
  if (_iter != nil)
    [_iter free];
  
  if (_config != nil)
    [_config free];
  
  return [super free];
}


//// Config tree reading and writing

//
// Read the config from a source
//  
// @param source the source of the config
// @param name   the name of the source
// 
// @return success
// 

- (BOOL) read :(id <DTextReadable>) source :(const char *) name
{
  BOOL ok = NO;

  DConfigReader *reader = [DConfigReader new];
   
  ok = [reader parse :source :name :self];
    
  [reader free];
    
  return ok;
}

//
// Write the config to a destination
// 
// @param destination the destination of the config
// @param name        the name of the destination (not used)
// 
// @return success
// 

- (BOOL) write :(id <DTextWritable>) destination :(const char *) name
{
  BOOL   ok   = NO;
  
  if (destination == nil)
  {
    WARNING(DW_INVALID_ARG, "destination");
  }
  else
  {
    DConfigWriter *writer = [DConfigWriter new];
    DText         *section = nil;
    DText         *option  = nil;
    DText         *value   = nil;
      
    [writer startConfig :destination];

    // iterate the config tree
    section = [_iter root];
    while (section != nil)
    {
      [writer section :[section cstring]];
      
      if ([_iter hasChildren])
      {
        option = [_iter child];
        while (option != nil)
        {
          if ([_iter hasChildren])
          {
            value = [_iter child];
            
            [writer option :[section cstring] :[option cstring] :[value cstring]];
              
            [_iter parent];
          }
            
          option = [_iter next];
        }
        [_iter parent];
      }
        
      section = [_iter next];
    }

    [writer endConfig];
      
    [writer free];
    
    ok = YES;
  }
  
  return ok;
}


//// Options methods

//
// Set the value for a section,option
// 
// @param section   the section for the option
// @param option    the option
// @param value     the value for the option
// 
// @return success
// 

- (BOOL) set :(const char *) section :(const char *) option :(const char *) value
{
  BOOL ok = NO;
  
  if ((section == NULL) || (strlen(section) == 0))
  {
    WARNING(DW_INVALID_ARG, "section");
  }
  else if ((option == NULL) || (strlen(option) == 0))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else if ((value == NULL) || (strlen(value) == 0))
  {
    WARNING(DW_INVALID_ARG, "value");
  }
  else
  {
    DText *text = [_iter root];
    
    if (text == nil)
    {
      text = [DText alloc];
      
      [text init :section];
      
      [_iter append :text];
    }
    else
    {
      while (text != nil)
      {
        if ([text icompare :section] == 0)
          break;
      
        text = [_iter next];
      }
    
      if (text == nil)
      {
        text = [DText alloc];
      
        [text init :section];
      
        [_iter after :text];
      }
    }
    
    // iter on section
    if ([_iter hasChildren])
    {
      text = [_iter child];
      while (text != nil)
      {
        if ([text icompare :option] == 0)
          break;
        
        text = [_iter next];
      }
      
      if (text == nil) // new option for section: add child
      {
        text = [DText alloc];
        
        [text init :option];
        
        [_iter after :text];
      }
    }
    else // first option for section: add child
    {
      text = [DText alloc];
      
      [text init :option];
      
      [_iter append :text];
    }

    // iter on option
    if ([_iter hasChildren]) // option already exist, set value
    {
      text = [_iter child];
      
      [text set :value];
    }
    else 
    {
      text = [DText alloc]; // add value to new option
      
      [text init :value];
      
      [_iter append :text];
    }
    
    ok = YES;
  }
  
  return ok;
}

//
// Get the value of a section,option
// 
// @param section   the section for the option
// @param option    the option
// 
// @return the value (or NULL)
// 

- (const char *) get :(const char *) section :(const char *) option
{
  DText *value = nil;
  
  if ([self has :section :option])
  {
    value = [_iter child];
  }
  
  return (value == nil ? NULL : [value cstring]);
}

//
// Check if the config tree has a section
// 
// @param section   the section
// 
// @return has it ?
// 

- (BOOL) has :(const char *) section
{
  DText *text = nil;
  
  if ((section == NULL) || (strlen(section) == 0))
  {
    WARNING(DW_INVALID_ARG, "section");
  }
  else
  {
    text = [_iter root];
  
    while (text != nil)
    {
      if ([text icompare :section] == 0)  // leave the iter on the section
        break;
    
      text = [_iter next];
    }
  }
  
  return (text != nil);
}

//
// Check if the config tree has a section,option
// 
// @param section   the section
// @param option    the option
// 
// @return has it ?
// 

- (BOOL) has :(const char *) section :(const char *) option
{
  DText *text = nil;

  if ((option == NULL) || (strlen(option) == 0))
  {
    WARNING(DW_INVALID_ARG, "option");
  }
  else
  {
    if ([self has :section])
    {
      text = [_iter child];
    
      while (text != nil)
      {
        if ([text icompare :option] == 0) // leave the iter on the option
          break;
      
        text = [_iter next];
      }
    }
  }
  
  return (text != nil);
}

//
// Remove a section (and all its options) from the config tree
// 
// @param section   the section
// 
// @return success
// 

- (BOOL) remove :(const char *) section
{
  BOOL ok = NO;

  if ([self has :section])
  {
    DText *sect   = [_iter object];
    DText *option = nil;
    
    option = [_iter child];
    while ((option != sect) && (option != nil))
    {
      // iter on option
      if ([_iter hasChildren])
      {
        [_iter child];
        [_iter remove];
      }
      option = [_iter remove];
    }
      
    // also remove the section 
    if (option == sect)
    {
      [_iter remove];
        
      ok = YES;
    }
  }
  
  return ok;
}

//
// Remove a section,option from the config tree
// 
// @param section   the section
// @param option    the option
// 
// @return success
// 

- (BOOL) remove :(const char *) section :(const char *) option
{
  BOOL ok = NO;
  
  if ([self has :section :option])
  {
    if ([_iter hasChildren])
    {
      [_iter child];
      [_iter remove];
    }
    [_iter remove];
      
    ok = YES;
  }
  
  return ok;
}


//// List methods

//
// Return a list with all the sections in the config tree
// 
// @return a (new) list with all the sections
// 

- (DList *) sections
{
  DText *section = nil;
  DList *list    = [DList new];
  
  section = [_iter root];
  while (section != nil)
  {
    [list append :[section copy]];
    
    section = [_iter next];
  }
  
  return list;
}

//
// Return a list with all the options in a section
// 
// @param section   the section for the opstions
// 
// @return a (new) list with all the options (or nil for unkown section)
// 

- (DList *) options :(const char *) section
{
  DList *list = nil;
  
  if ([self has :section])
  {
    DText *option = nil;
    
    list = [DList new];

    option = [_iter child];
    while (option != nil)
    {
      [list append :[option copy]];
      
      option = [_iter next];
    }
  }
  
  return list;
}


//// Call back methods for the config parser (private)

//
// Process the start of the config file
// 
// @return success
// 

- (BOOL) startConfig
{
  // not much to do

  return YES;
}

//
// Process the end of the config file
// 
// @return success
//

- (BOOL) endConfig
{
  // not much to do
  
  return YES;
}

//
// Process the (start of a) section
// 
// @param name   the name of the section
// 
// @return success
// 

- (BOOL) section :(const char *) name
{
  // Not much to do

  return YES;
}

//
// Process an option
// 
// @param section  the name of the section
// @param option   the name of the option
// @param value    the value of the option
// 
// @return success
// 

- (BOOL) option :(const char *) section :(const char *) option :(const char *) value
{
  return [self set :section :option :value];
}
    
//
// Write a comment in the config file
// 
// @param comment  the comment text
// 
// @return success
// 

- (BOOL) comment :(const char *) comment
{
  // not much to do
  
  return YES;
}

//
// An error is found during the parsing of the file
// 
// @param number       the error number
// @param name         the name of the config file
// @param lineNumber   the line number in the config file
// @param columnNumber the column number in the config file
// 
// @return success
// 

- (void) error :(int) number :(const char *) name :(int) lineNumber :(int) columnNumber;
{
  fprintf(stderr, "%s, line %d.%d: code:%d\n", name, lineNumber, columnNumber, number);
}
        
@end
#endif

/*==========================================================================*/
